﻿using System;
using System.Collections.Generic;
using System.Text;
using Pixysoft.Framework.Logs.Controller;
using Pixysoft.Framework.Verifications;
using Pixysoft.Reflection;
using System.Reflection;
using System.Diagnostics;
using System.Windows.Forms;

namespace Pixysoft.Framework.Logs.Core
{
    class CoreLogger : ILogger
    {
        public event ThrowUnhandledExcpetion OnThrowUnhandledExcpetion;

        private static bool default_debug_full_path = false;	// true = display full path, even when

        private static bool default_debug_thread_id = true;	// true = display win32 and managed 

        bool isDisposed = false;

        LoggerSetting setting = null;

        CoreLoggerWriter writer = null;

        public CoreLogger(string filename, string modulename)
        {
            if (string.IsNullOrEmpty(filename))
                filename = StringHelper.default_log_prefix;

            if (string.IsNullOrEmpty(modulename))
                modulename = "";

            this.setting = SettingController.Instance.getSetting(filename, modulename);
        }

        public string Register()
        {
            return LogRegisterController.Instance.Register(setting.GetLogFilePath(), setting.Filename);
        }

        /// <summary>
        /// 注册没有捕捉的异常情况
        /// </summary>
        public void RegisterUnhandleExeption()
        {
            //http://www.cnblogs.com/yinzixin/archive/2009/12/24/1631633.html

            Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);

            AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            Fatal("PLEASE ATTENTATION!! UNHANDLE EXCEPTION THROW FROM APPDOMAIN !!");

            if (e.ExceptionObject == null)
                return;

            Exception ex = e.ExceptionObject as Exception;

            string recordId = null;

            if (ex == null)
                recordId = Fatal(e.ExceptionObject.ToString());
            else
                recordId = Fatal(ex);

            if (OnThrowUnhandledExcpetion != null)
                OnThrowUnhandledExcpetion(setting.Filename, recordId);
        }

        private void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            Fatal("PLEASE ATTENTATION!! UNHANDLE EXCEPTION THROW FROM APPLICATION !!");

            string recordId = Fatal(e.Exception);

            if (OnThrowUnhandledExcpetion != null)
                OnThrowUnhandledExcpetion(setting.Filename, recordId);
        }



        public void Backup()
        {
            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                _writer.TryBackup();
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }

        public string[] Unpackage(string zipFileFullName)
        {
            if (string.IsNullOrEmpty(zipFileFullName))
                throw Exceptions.VerificationFailedException(zipFileFullName);

            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                string[] result = LogPackageController.Instance.Unpackage(zipFileFullName);

                return result;
            }
            finally
            {
                _writer.TryOpen();//如果解压出错 会导致没有开启

                _writer.Unlock();
            }
        }

        public ILogPackage Package()
        {
            return Package(0);
        }

        public ILogPackage Package(int millicSecAgo)
        {
            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                ILogPackage result = LogPackageController.Instance.Package(this.setting, millicSecAgo);

                return result;
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }


        public ILogReport GetRecentReport(int millisec)
        {
            return GetRecentReport(millisec, LogLevel.ALL);
        }

        public ILogReport GetRecentReport(int millisec, LogLevel level)
        {
            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                ILogReport result = LogReportController.Instance.ParseRecentReport(this.setting, millisec, level);

                return result;
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }

        public ILogReport GetReport(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw Exceptions.VerificationFailedException(id);

            return GetReport(id, StringHelper.log_reportspan);
        }

        public ILogReport GetReport(string id, int millisec)
        {
            if (string.IsNullOrEmpty(id))
                throw Exceptions.VerificationFailedException(id);

            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                ILogReport result = LogReportController.Instance.GetReport(this.setting, id, millisec);

                return result;
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }

        public ILogRecord GetRecord(string id)
        {
            if (string.IsNullOrEmpty(id))
                throw Exceptions.VerificationFailedException(id);

            return GetRecord(id, false);
        }

        public ILogRecord GetRecord(string id, bool crossfiles)
        {
            if (string.IsNullOrEmpty(id))
                throw Exceptions.VerificationFailedException(id);

            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                ILogRecord result = LogReportController.Instance.GetRecord(this.setting, id, crossfiles);

                return result;
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }


        public int CountOfFiles()
        {
            return CountOfFiles(0);
        }

        public int CountOfFiles(int millicSecAgo)
        {
            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                int result = LogPackageController.Instance.GetCountOfLogFiles(this.setting, millicSecAgo);

                return result;
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }



        public void Format()
        {
            Format(false);
        }

        public void Format(bool crossfiles)
        {
            CoreLoggerWriter _writer = this.GetAvailableWriter();

            _writer.Lock();

            try
            {
                _writer.TryClose();

                LogFormatController.Instance.Format(setting.GetLogFilePath(), setting.Filename, crossfiles);
            }
            finally
            {
                _writer.TryOpen();

                _writer.Unlock();
            }
        }

        public string All(string format, params object[] paras)
        {
            return Logging(LogLevel.ALL, format, paras);
        }

        public string Debug(string format, params object[] paras)
        {
            if (setting.LogLevel < LogLevel.DEBUG)
            {
                return null;
            }

            return Logging(LogLevel.DEBUG, format, paras);
        }

        public string Error(string format, params object[] paras)
        {
            if (setting.LogLevel < LogLevel.ERROR)
            {
                return null;
            }

            return Logging(LogLevel.ERROR, format, paras);
        }

        public string Info(string format, params object[] paras)
        {
            if (setting.LogLevel < LogLevel.INFO)
            {
                return null;
            }

            return Logging(LogLevel.INFO, format, paras);
        }

        public string Warn(string format, params object[] paras)
        {
            if (setting.LogLevel < LogLevel.WARN)
            {
                return null;
            }

            return Logging(LogLevel.WARN, format, paras);
        }

        public string Fatal(string format, params object[] paras)
        {
            if (setting.LogLevel < LogLevel.FATAL)
            {
                return null;
            }

            return Logging(LogLevel.FATAL, format, paras);
        }

        public string Fatal(Exception ex)
        {
            if (setting.LogLevel < LogLevel.FATAL)
            {
                return null;
            }

            return Logging(ex);
        }

        public void Close()
        {
            WriterController.Instance.RemoveWriter(setting);

            this.writer = null;
        }


        private string Logging(LogLevel level, string format, params object[] paras)
        {
            if (string.IsNullOrEmpty(format))
                return null;

            //针对无参数的情况 要额外处理

            string content = null;

            if (paras == null || paras.Length == 0)
            {
                content = format;
            }
            else
            {
                try
                {
                    content = string.Format(format, paras);
                }
                catch
                {
                    content = "LOG FORMAT ERROR, ORG.TEXT = ::" + format;
                }
            }

            string id = StringHelper.GetId();

            OutputToFile(level, content, id);

            if (setting.PrintToConsole)
            {
                OutputToConsole(level, content);
            }

            return id;
        }

        private void OutputToFile(LogLevel level, string content, string id)
        {
            StringBuilder builder = new StringBuilder();

            AppendStartElement(builder, StringHelper.GetLogLevelString(level), id);

            AppendBody(builder, content);

            AppendEndElement(builder);

            GetAvailableWriter().Write(builder.ToString());
        }

        private void AppendStartElement(StringBuilder builder, string level, string id)
        {
            builder.Append("<");

            builder.Append(StringHelper.default_element_log);

            builder.Append(" ");

            builder.AppendFormat(StringHelper.default_attribute_level + "=\"{0}\"", level);

            builder.Append(" ");

            builder.AppendFormat(StringHelper.default_attribute_time + "=\"{0}\"", StringHelper.GetRawDateTime());

            builder.Append(" ");

            builder.AppendFormat(StringHelper.default_attribute_id + "=\"{0}\"", id);

            builder.Append(" ");

            builder.AppendFormat(StringHelper.default_attribute_module + "=\"{0}\"", setting.Modulename);

            builder.AppendLine(">");
        }

        private void AppendBody(StringBuilder builder, string content)
        {
            if (!string.IsNullOrEmpty(content))
            {
                //09-12-05 bug

                content = StringHelper.RegularLogContent(content);
            }

            builder.AppendLine(content);
        }

        private void AppendEndElement(StringBuilder builder)
        {
            builder.AppendLine("</" + StringHelper.default_element_log + ">");
        }

        private string OutputToConsole(LogLevel level, string content)
        {
            StringBuilder builder = new StringBuilder();

            builder.AppendLine(string.Format("{0}\t{1}\t[{2}::{3}]\t{4}",
                StringHelper.GetRawDateTime(), level.ToString().ToUpper(), setting.Filename, setting.Modulename, level));

            builder.Append("\t").AppendLine(content);

            string log = builder.ToString();

            Console.WriteLine(log);

            return log;
        }


        private string Logging(Exception ex)
        {
            //使用stack trace 跟踪所有frame，并且记录，方便日后进行日志的合并查询

            Exception parseException = ex;

            StringBuilder builder = new StringBuilder();

            //10-04-19 针对expected的内部异常跟踪

            if (ex is UnexpectedException && ex.InnerException != null)
                parseException = ex.InnerException;
            else if (ex is ExternalException && ex.InnerException != null)
                parseException = ex.InnerException;

            string content = parseException.ToString();

            string id = StringHelper.GetId();

            OutputExceptionToFile(builder, ex, id);

            if (setting.PrintToConsole)
            {
                OutputToConsole(LogLevel.FATAL, content);
            }

            OutputExceptionToDebug(ex);

            return id;
        }

        private void OutputExceptionToFile(StringBuilder builder, Exception ex, string id)
        {
            AppendStartElement(builder, LogLevelCollection.FATAL, id);

            //2010-03-11 bug!! cannot use message element 

            //builder.AppendLine("<" + StringHelper.default_element_message + ">");

            AppendBody(builder, ex.ToString());

            //builder.AppendLine(ex.Message);

            //builder.AppendLine("</" + StringHelper.default_element_message + ">");

            System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(ex, true);

            //09-12-25 进一步对异常方法体进行跟踪

            LogExceptionMethodBody(builder, stack, ex, id);

            if (stack.FrameCount > 0)
                foreach (System.Diagnostics.StackFrame frame in stack.GetFrames())
                {
                    try
                    {
                        builder.AppendLine(string.Format(StringHelper.default_tag_frame,
                            StringHelper.RegularLogContent(Pixysoft.Reflection.ReflectHelper.Instance.RegularTypeName(frame.GetMethod().DeclaringType.FullName)),
                            StringHelper.RegularLogContent(frame.GetMethod().Name),
                            StringHelper.RegularLogContent(frame.GetMethod().Module.Name),
                            StringHelper.RegularLogContent(frame.GetFileName()),
                            frame.GetILOffset(),
                            frame.GetFileLineNumber(),
                            frame.GetFileColumnNumber(),
                            id));
                    }
                    catch
                    {
                    }
                }

            AppendEndElement(builder);

            GetAvailableWriter().Write(builder.ToString());
        }

        private void OutputExceptionToDebug(Exception ex)
        {
            //10-03-08 

            System.Diagnostics.StackTrace stack = new System.Diagnostics.StackTrace(ex, true);

            if (stack.FrameCount > 0)
                foreach (System.Diagnostics.StackFrame frame in stack.GetFrames())
                {
                    string content = GetPrefix(frame) +
                        string.Format("{0}::{1}.{2}",
                        frame.GetMethod().Module.Name,
                        frame.GetMethod().DeclaringType.FullName,
                        frame.GetMethod().Name);

                    System.Diagnostics.Debug.WriteLine(content);
                }
        }

        private void LogExceptionMethodBody(StringBuilder builder, System.Diagnostics.StackTrace stack, Exception ex, string id)
        {
            if (stack.FrameCount == 0)
                return;

            StackFrame exceptionframe = stack.GetFrame(0);

            if (exceptionframe == null)
                return;

            MemberInfo callingmethod = null;

            ILInstruction instruction = null;

            try
            {
                instruction = MethodBodyReflector.GetThrowingInstruction(ex);
            }
            catch
            {
                return;
            }

            if (instruction == null)
                return;

            switch (instruction.InstructionType)
            {
                case ILInstructionType.Ctor:
                    {
                        callingmethod = instruction.GetConstructorInfo();

                        break;
                    }
                case ILInstructionType.Method:
                    {
                        callingmethod = instruction.GetMethodInfo();

                        break;
                    }
                case ILInstructionType.Field:
                    {
                        callingmethod = instruction.GetFieldInfo();

                        break;
                    }
                default:
                    {
                        break;
                    }
            }

            if (callingmethod == null)
                return;

            //10-02-14 必须也要内容规则化！

            try
            {
                //2010-03-16 重新规格化异常的记录

                builder.AppendLine(string.Format(StringHelper.default_tag_frame,
                    StringHelper.RegularLogContent(Pixysoft.Reflection.ReflectHelper.Instance.RegularTypeName(callingmethod.DeclaringType.FullName)),
                    StringHelper.RegularLogContent(callingmethod.Name),
                    StringHelper.RegularLogContent(callingmethod.DeclaringType.Assembly.GetName().Name),
                    StringHelper.RegularLogContent(exceptionframe.GetFileName()),
                    exceptionframe.GetILOffset(),
                    exceptionframe.GetFileLineNumber(),
                    exceptionframe.GetFileColumnNumber(),
                    id));
            }
            catch
            {
            }
        }

        private static string GetPrefix(System.Diagnostics.StackFrame caller)
        {
            string prefix = "";
            try
            {
                string filename = caller.GetFileName();

                // we display full path always when running under debugger -
                // this allows double-clicking the line in the output window
                if (!default_debug_full_path && !Debugger.IsAttached)
                {
                    // extract file name from path
                    int index = filename.LastIndexOf('\\');
                    if (index > 0)
                    {
                        int len = filename.Length;
                        filename = filename.Substring(index + 1, len - index - 1);
                    }
                }

                // add line number in parens
                prefix = String.Format("{0}({1}): ", filename, caller.GetFileLineNumber());

                if (default_debug_thread_id)
                {
                    // display thread id -
                    // we want to see the same thread id that the debugger displays
                    int win32_threadid = System.Threading.Thread.CurrentThread.ManagedThreadId;
                    // and the .Net logical thread id
                    int managed_threadid = System.Threading.Thread.CurrentThread.ManagedThreadId;
                    string id = String.Format("[{0:X}:{1:X}]  ", win32_threadid, managed_threadid);
                    prefix = prefix + id;
                }
            }
            catch
            {
            }

            return prefix;
        }



        public ILoggerSetting Setting
        {
            get
            {
                if (string.IsNullOrEmpty(this.setting.Modulename))
                    return SettingController.Instance.getGroupSetting(this.setting.Filename);

                return this.setting;
            }
        }

        private CoreLoggerWriter GetAvailableWriter()
        {
            if (this.isDisposed)
                throw Exceptions.ManualVerificationFailedException("logger is disposed.");

            if (this.writer == null)
            {
                return ApplySetting();
            }

            if (this.writer.IsClosed)
            {
                this.writer.TryOpen();

                return this.writer;
            }

            if (this.writer.IsDisposed)
            {
                return ApplySetting();
            }

            return this.writer;
        }

        private CoreLoggerWriter ApplySetting()
        {
            this.setting.SetRunning();

            this.writer = WriterController.Instance.CreateWriter(this.setting);

            ConsoleHelper.Writeline("get writer with config:" + this.setting.ToString());

            return this.writer;
        }
    }
}
